﻿  

namespace Common.Algorithm.Core;


/// <summary>
/// 粒子群算法  全局最优能力强   但是精度差
/// 测试函数地址 ： en.wikipedia.org/wiki/Test_functions_for_optimization
/// </summary>
public class ParticleSwarmService
{

    public static List<double[]> records { get; set; } = new List<double[]>();

    private static Random random = new Random();
    private static VectorBuilder<double> vectorBuilder = Vector<double>.Build;
    //private static MatrixBuilder<double> matrixBuilder = Matrix<double>.Build;

    public List<Particle> particles = new List<Particle>();

    public static double tolarence = 0.001;

    /// <summary>
    /// 场
    /// </summary>
    public static Dictionary<Vector<double>, double> field { get; set; } = new Dictionary<Vector<double>, double>();

    public static Func<Vector<double>, double> denseField { get; set; } = (v) => { return 0; };


    public void RunAlgm(int numberOfGeneration, Func<Vector<double>, double> func)
    {
        double unit = 1d / numberOfGeneration;
        while (numberOfGeneration > 0)
        {
            numberOfGeneration--;
            double inertiaRatio = unit * numberOfGeneration;//惯性减小系数

            //team best vecotr 
            Vector<double> groupBestLocation = particles.First().currentLocation;

            Dictionary<Vector<double>, double> pairs = new Dictionary<Vector<double>, double>();

            particles.ForEach(p => pairs.Add(p.currentLocation, func(p.currentLocation)));
            groupBestLocation = pairs.OrderBy(p => p.Value).First().Key;

            foreach (var particle in particles)
            {
                var curLoc = particle.currentLocation;
                int dimension = curLoc.Count();
                ///person 更新个人最佳方向
                var personBestVelocity = (particle.FindMinPoint(curLoc) - curLoc).Normalize(dimension) * random.NextDouble() * inertiaRatio;
                particle.personBestVelocity = personBestVelocity * tolarence;
                //update groupVelocity 更新全队最佳方向
                var groupBestVelocity = (groupBestLocation - curLoc).Normalize(dimension) * random.NextDouble();
                particle.groupBestVelocity = groupBestVelocity * tolarence;

                var currentVelocity = (particle.currentVelocity + groupBestVelocity + personBestVelocity) * tolarence * 3;
                //update lcoation 更新个人位置
                particle.currentLocation += currentVelocity;
                //update curVelocity  更新个人惯性
                particle.currentVelocity = currentVelocity * random.NextDouble() * inertiaRatio;

            }

            //历史记录
            particles.ForEach(p =>
               p.historyLocation.Append(p.currentLocation));

            records.AddRange(particles.Select(p=>new double[] { p.currentLocation[0] , p.currentLocation[1] }));
        }
        
    }

    #region  离散场   非稠密   仅固定位置有值   故求最优判断需依赖附近点
    public ParticleSwarmService(Dictionary<Vector<double>, double> _field)
    {
        field = _field;
        var maxRange = Math.Max(
            field.Keys.Max(k => k[0]),
            field.Keys.Max(k => k[1]));

        for (int i = 0; i < 10; i++)
        {
            Particle particle
                = new Particle(2, maxRange) { id = i, Name = i.ToString() };
            particles.Add(particle);
        }
    }

    /// <summary>
    /// 离散场空间
    /// </summary>
    /// <param name="numberOfGeneration"></param>
    public void RunDiscreteAlgm(int numberOfGeneration)
    {
        //离散场需要找到当前附近点的数据
        Func<Vector<double>, double> func = (loc) =>
        {
            ///场内距离当前粒子比较近的点集合
            var odFiled = field.OrderBy(f => (f.Key - loc).L2Norm()).ToList();
            //当前粒子周围8个中最优点
            var curLoc = odFiled.GetRange(0, 8).OrderBy(p => field[p.Key]).First().Key;
            //var curLoc = odFiled.First().Key;//场内距离当前粒子最近点  

            ///team 更新最佳位置
            //double signData = field[particle.currentLocation]; 
            var signData = field[curLoc];
            return signData;
        };

        RunAlgm(numberOfGeneration, func);

    }
    #endregion


    #region  解析连续的稠密场  任何位置都有公式可求值  
    public ParticleSwarmService(IEnumerable<double[]> ranges, double step)
    {
        for (int i = 0; i < 20; i++)
        {
            Particle particle
                = new Particle(ranges, step) { id = i, Name = i.ToString() };
            particles.Add(particle);
        } 
    }

  
    /// <summary>
    /// 稠密解空间
    /// </summary>
    /// <param name="numberOfGeneration"></param>
    public void RunDenseAlgm(int numberOfGeneration)
    {
        RunAlgm(numberOfGeneration, denseField); 
    }

    #endregion


    /// <summary>
    /// 
    /// </summary>
    public class Particle
    {

        /// <summary>
        /// maxRange
        /// </summary>
        /// <param name="dimension"></param>
        /// <param name="maxRange">场的最大边界</param>
        public Particle(int dimension, double maxRange)
        {
            double[] array = new double[dimension];
            for (int i = 0; i < dimension; i++)
            {
                array.Append(random.NextDouble() * maxRange);
            }
            array = array.Select(ar => ar * 1.00d).OrderBy(ar => random.Next()).ToArray();
            this.currentLocation = vectorBuilder.Dense(array);

            array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
            this.currentVelocity = vectorBuilder.Dense(array);

            array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
            this.personBestVelocity = vectorBuilder.Dense(array);

            array = array.Select(ar => ar * 0.01d).OrderBy(ar => random.Next()).ToArray();
            this.groupBestVelocity = vectorBuilder.Dense(array);

            historyLocation = new Vector<double>[dimension];
        }

        /// <summary>
        /// 各维度不同范围的初始化定义
        /// </summary>
        /// <param name="_ranges">给出各个维度的定义域  </param>
        public Particle(IEnumerable<double[]> _ranges, double step)
        {
            this.ranges = _ranges.ToList();

            this.currentLocation = vectorBuilder.Dense(GenRandomArray(_ranges, 1d));

            this.currentVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

            this.personBestVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

            this.groupBestVelocity = vectorBuilder.Dense(GenRandomArray(_ranges, step));

            historyLocation = new Vector<double>[_ranges.Count()];
        }

        private double[] GenRandomArray(IEnumerable<double[]> ranges, double ratio = 1.0d)
        {
            var dimension = ranges.Count();
            double[] array = new double[dimension];

            for (int i = 0; i < dimension; i++)
            {
                var rand = random.NextDouble();
                double[] range = ranges.ToList()[i];
                array[i] = range[0] + rand * (range[1] - range[0]);
                //array.Append();
            }
            array = array.Select(ar => ar * ratio).ToArray();
            return array;
        }

        public Vector<double> FindMinPoint(Vector<double> point)
        {
            var dim = point.Count();
            Vector<double> newPnt = vectorBuilder.Dense(dim);

            Dictionary<Vector<double>, double> pairs = new Dictionary<Vector<double>, double>();
            pairs.Add(point, denseField(point));
            for (int j = 0; j < ranges.Count; j++)
            {
                var range = ranges[j];
                var span = range[1] - range[0];
                var arr = new double[dim];
                arr[j] = 1;
                var vec = vectorBuilder.DenseOfArray(arr);
                Vector<double> min = point - vec * 0.02;
                Vector<double> max = point + vec * 0.02;
                pairs.Add(min, denseField(min));
                pairs.Add(max, denseField(max));
            }
            newPnt = pairs.OrderBy(p => p.Value).First().Key;
            return newPnt;
        }

        public List<double[]> ranges { get; set; }

        public int id { get; set; } = 0;
        public string Name { get; set; } = string.Empty;

        public Vector<double> currentLocation { get; set; }
        public Vector<double> currentVelocity { get; set; }
        public Vector<double> personBestVelocity { get; set; }
        public Vector<double> groupBestVelocity { get; set; }

        public Vector<double>[] historyLocation { get; set; }

    }
}
